/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.security;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * An internal utility class which provides functionality for generating unique
 * {@link String} identifiers within a given Java runtime.  
 * </p> 
 */
public class UniqueStringGenerator
{		
	private static final Logger logger = 
		LoggerFactory.getLogger(UniqueStringGenerator.class);
	
	/**
	 * <p>
	 * Generates the random numbers from which unique {@link String strings} 
	 * are generated.
	 * </p>
	 */
	private SecureRandom numberGenerator;
	
	/**
	 * <p>
	 * Calculates the digest of the textual representation of the number generated 
	 * by the {@link SecureRandom} generator.
	 * </p>
	 */
	private MessageDigest messageDigestAlgorithm;
	
	/**
	 * <p>
	 * The algorithm with which the {@link SecureRandom} generator is initialised. This is 
	 * set to <i>{@value #SECURE_RANDOM_ALGORITHM_ID}</i>.
	 * </p>
	 */
	private static final String SECURE_RANDOM_ALGORITHM_ID="SHA1PRNG";
	
	/**
	 * <p>
	 * The algorithm with which the {@link MessageDigest} functionality is initialised. This is 
	 * set to <i>{@value #MESSAGE_DIGEST_ALGORITHM_ID}</i>.
	 * </p>
	 */		
	private static final String MESSAGE_DIGEST_ALGORITHM_ID="SHA-1";
	
	/**
	 * <p>
	 * Collections instance which holds a list of all the unique identifiers generated
	 * by this class. This is used to ensure that an identifier is never used more than once.
	 * </p> 
	 */		
	private ArrayList<String> usedIdentifiers;
		
	
	/**
	 * Default constructor.
	 */
	public UniqueStringGenerator()
	{
		try
		{
			numberGenerator = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM_ID);
			messageDigestAlgorithm=MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_ID);
			usedIdentifiers=new ArrayList<String>();
		}
		catch (NoSuchAlgorithmException ns_a_exce)
		{
			String errorMessage="OBIX Configuration Framework Error: Unable to initialize secure-random-number "+
				"generator, and message-digest for generating unique hashcodes!\n" + ns_a_exce.getMessage();
			logger.warn(errorMessage, ns_a_exce);			
		}
	}
	
	/**
	 * <p>
	 * This generates a {@link String string} that is unique within the context of the current Java runtime and the instance
	 * upon which the method is invoked. Put differently, subsequent invocations of this method, in a given runtime and on the same instance,
	 * will never return a given value more than once.
	 * </p>
	 * 
	 * @return A unique {@link String string}
	 */
	public synchronized String generateUniqueString() 
	{
		String result;
		
		String uniqueString=generateString();
		
		while (usedIdentifiers.contains(uniqueString))
			uniqueString=generateString();
			
		result = uniqueString;
		usedIdentifiers.add(result);
		
		return result;
	}
	
	/**
	 * <p>
	 * Generates a {@link String string} by getting the next integer from the {@link SecureRandom}
	 * generator, and calculating the digest of the {@link String string} using the {@link MessageDigest}
	 * instance.
	 * </p> 
	 * 
	 * @return A {@link String string} digest of the next random number generated by the {@link SecureRandom} 
	 * generator.
	 */
	private synchronized String generateString()
	{
		String result; 
		
		//generate a random number
		String randomNumber = new Integer(numberGenerator.nextInt()).toString();

		//get the digest of the number string
		byte[] dataBytes = messageDigestAlgorithm.digest(randomNumber.getBytes());

		result= hexEncode(dataBytes);
		
		return result;			
	}

	/**
	 * <p>
	* This method is taken from an example in David Flanagan's book
	* "Java In A Nutshell", and converts a byte array into a String
	* of hex characters.
	* </p>
	*
	* @param inputData The byte array to be converted into hex
	* @return A {@link String string} of hex characters corresponding to the input array  
	*/
	private String hexEncode( byte[] inputData)
	{
		StringBuffer result = new StringBuffer();
	  	char[] digitMap = {'0', '1', '2', '3', '4','5','6','7','8','9','a','b','c','d','e','f'};
	  	byte tempArray;
	  	for (int i = 0; i < inputData.length; ++i) 
	  	{
			tempArray = inputData[i];
			result.append( digitMap[ (tempArray&0xf0) >> 4 ] );
			result.append( digitMap[ tempArray&0x0f] );
	  	}
	  	
	  	return result.toString();
	}		
}